package com.awesome.db;

import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;

import com.awesome.db.annotation.Column;
import com.awesome.db.annotation.Delete;
import com.awesome.db.annotation.Insert;
import com.awesome.db.annotation.ObjParam;
import com.awesome.db.annotation.Param;
import com.awesome.db.annotation.Select;
import com.awesome.db.annotation.Update;
import com.awesome.db.bean.PrimaryKeyColumnInfoBean;
import com.awesome.db.bean.TableInfoBean;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Description:数据库管理者类
 */

public class DatabaseManager {
    private DatabaseHelper mDatabaseHelper;
    private SQLiteDatabase mSqLiteDatabase;

    public DatabaseManager() {
    }

    /**
     * Author: JFangZ
     * Email:janezhang1901@163.com
     * Date:2020/4/19 21:50
     * Description:获取参数集合
     *
     * @param method 被调用的方法
     * @param args   参数值集合
     */
    private Map<String, Object> getParamMap(Method method, Object[] args) throws Exception {
        // 定义一个集合来保存参数
        Map<String, Object> paramMap = new HashMap<>();
        if (isObjParam(method)) {
            Object arg = args[0];
            Class<?> clazz = arg.getClass();
            while (clazz != null) {
                for (Field field : clazz.getDeclaredFields()) {
                    Column column = field.getAnnotation(Column.class);
                    if (column == null) {
                        continue;
                    }
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }
                    paramMap.put(mDatabaseHelper.getColumnName(field, column), field.get(arg));
                }
                clazz = clazz.getSuperclass();
            }
        } else {
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            for (int i = 0; i < parameterAnnotations.length; i++) {
                boolean hasParamAnnotation = false;
                for (Annotation annotation : parameterAnnotations[i]) {
                    if (!(annotation instanceof Param)) {
                        continue;
                    }
                    String name = ((Param) annotation).value();
                    paramMap.put(name, args[i]);
                    hasParamAnnotation = true;
                }
                if (!hasParamAnnotation) {
                    throw new Exception("The param must be have Param Annotation");
                }
            }
        }
        return paramMap;
    }

    private boolean isObjParam(Method method) {
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        return parameterAnnotations.length == 1
                && parameterAnnotations[0].length == 1
                && (parameterAnnotations[0][0] instanceof ObjParam);
    }

    /**
     * Author: JFangZ
     * Email:janezhang1901@163.com
     * Date:2020/4/19 21:50
     * Description:格式化 sql,将参数值替换到 sql 语句中
     *
     * @param sql      自定义 sql
     * @param paramMap 参数集合
     */
    private String formatQuerySql(String tag, String sql, Map<String, Object> paramMap) {
        if (mDatabaseHelper.mShowSql) {
            Log.i(tag, "before--->" + sql);
        }
        // 找到 #{} 参数名占位符
        Pattern pattern = Pattern.compile("#\\{([a-z]|[A-Z]|[0-9])+\\}");
        Matcher matcher = pattern.matcher(sql);
        while (matcher.find()) {
            String substring = sql.substring(matcher.start(), matcher.end());
            String key = substring.replace("#{", "")
                    .replace("}", "");
            Object paramValue = paramMap.get(key);

            if (paramValue == null) {
                sql = sql.replace(substring, "null");
            } else if (paramValue.getClass() == int.class || paramValue.getClass() == Integer.class
                    || paramValue.getClass() == long.class || paramValue.getClass() == Long.class
                    || paramValue.getClass() == float.class || paramValue.getClass() == Float.class
                    || paramValue.getClass() == double.class || paramValue.getClass() == Double.class) {
                sql = sql.replace(substring, "" + paramValue);
            } else if (paramValue.getClass().isEnum()) {
                sql = sql.replace(substring, "'" + paramValue.toString() + "'");
            } else if (paramValue.getClass().isArray()
                    && (paramValue.getClass().getComponentType() == byte.class || paramValue.getClass().getComponentType() == Byte.class)) {
                sql = sql.replace(substring, "'" + new String(Base64.encode((byte[]) paramValue, Base64.NO_WRAP)) + "'");
            } else {
                sql = sql.replace(substring, "'" + paramValue + "'");
            }
            matcher = pattern.matcher(sql);
        }
        if (mDatabaseHelper.mShowSql) {
            Log.i(tag, "after--->" + sql);
            Log.i(tag, "values--->" + paramMap);
        }
        return sql;
    }

    /**
     * Author: JFangZ
     * Email:janezhang1901@163.com
     * Date:2020/4/19 21:50
     * Description:格式化 sql,将参数值替换到 sql 语句中
     *
     * @param sql      自定义 sql
     * @param paramMap 参数集合
     */
    private SQLiteStatement formatSql(String tag, String sql, Map<String, Object> paramMap) {
        if (mDatabaseHelper.mShowSql) {
            Log.i(tag, "before--->" + sql);
        }
        // 找到 #{} 参数名占位符
        Pattern pattern = Pattern.compile("#\\{([a-z]|[A-Z]|[0-9])+\\}");
        Matcher matcher = pattern.matcher(sql);
        List<Object> paramValueList = new ArrayList<>();
        while (matcher.find()) {
            String substring = sql.substring(matcher.start(), matcher.end());
            sql = sql.replace(substring, "?");
            String key = substring.replace("#{", "")
                    .replace("}", "");
            paramValueList.add(paramMap.get(key));
            matcher = pattern.matcher(sql);
        }
        if (mDatabaseHelper.mShowSql) {
            Log.i(tag, "after--->" + sql);
            Log.i(tag, "values--->" + paramMap);
        }
        SQLiteStatement sqLiteStatement = mSqLiteDatabase.compileStatement(sql);
        int index = 1;
        for (Object paramValue : paramValueList) {
            if (paramValue == null) {
                // NULL
//                sql = sql.replace(substring, "null");
                sqLiteStatement.bindNull(index);
            } else if (paramValue.getClass() == int.class || paramValue.getClass() == Integer.class) {
                // INTEGER
//                sql = sql.replace(substring, "" + paramValue);
                sqLiteStatement.bindLong(index, (int) paramValue);
            } else if (paramValue.getClass() == long.class || paramValue.getClass() == Long.class) {
                // INTEGER
//                sql = sql.replace(substring, "" + paramValue);
                sqLiteStatement.bindLong(index, (long) paramValue);
            } else if (paramValue.getClass() == float.class || paramValue.getClass() == Float.class) {
                // FLOAT
//                sql = sql.replace(substring, "" + paramValue);
                sqLiteStatement.bindDouble(index, (float) paramValue);
            } else if (paramValue.getClass() == double.class || paramValue.getClass() == Double.class) {
                // FLOAT
//                sql = sql.replace(substring, "" + paramValue);
                sqLiteStatement.bindDouble(index, (double) paramValue);
            } else if (paramValue.getClass().isArray()
                    && (paramValue.getClass().getComponentType() == byte.class || paramValue.getClass().getComponentType() == Byte.class)) {
                // BLOB
                sqLiteStatement.bindBlob(index, (byte[]) paramValue);
            } else {
                // 其他都认为是 String,包括 enum
//                sql = sql.replace(substring, "'" + paramValue + "'");
                sqLiteStatement.bindString(index, paramValue.toString());
            }
            index++;
        }
        return sqLiteStatement;
    }


    /**
     * Author: JFangZ
     * Email:janezhang1901@163.com
     * Date:2020/4/19 21:50
     * Description:将原生 sql 查询的结果封装成对象
     */
    private Object getObj(String className, Cursor cursor) throws Exception {
        // 先获取所有属性
        Class<?> clazz = Class.forName(className);
        // 反射创建实例
        Object obj = clazz.newInstance();
        Map<String, Field> paramMap = new HashMap<>();
        while (clazz != null) {
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field field : declaredFields) {
                Column column = field.getAnnotation(Column.class);
                if (column == null) {
                    paramMap.put(field.getName(), field);
                } else {
                    paramMap.put(mDatabaseHelper.getColumnName(field, column), field);
                }
            }
            clazz = clazz.getSuperclass();
        }
        // 检查查出来的列有没有对应的属性,如果有就赋值
        String[] columnNameArray = cursor.getColumnNames();
        for (String columnName : columnNameArray) {
            // 找不到列名对应的变量,直接跳过
            Field field = paramMap.get(columnName);
            if (field == null) {
                continue;
            }
            int columnIndex = cursor.getColumnIndex(columnName);
            // 列对应的值为空,直接跳过
            if (cursor.getType(columnIndex) == Cursor.FIELD_TYPE_NULL) {
                continue;
            }
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            // 根据类型,设置属性的值
            Class<?> fieldType = field.getType();
            try {
                if (fieldType == int.class || fieldType == Integer.class) {
                    field.set(obj, cursor.getInt(columnIndex));
                } else if (fieldType == long.class || fieldType == Long.class) {
                    field.set(obj, cursor.getLong(columnIndex));
                } else if (fieldType == float.class || fieldType == Float.class) {
                    field.set(obj, cursor.getFloat(columnIndex));
                } else if (fieldType == double.class || fieldType == Double.class) {
                    field.set(obj, cursor.getDouble(columnIndex));
                } else if (fieldType == boolean.class || fieldType == Boolean.class) {
                    field.set(obj, Boolean.valueOf(cursor.getString(columnIndex)));
                } else if (fieldType == String.class) {
                    field.set(obj, cursor.getString(columnIndex));
                } else if (fieldType.isEnum()) {
                    Enum<?> enumValue = Enum.valueOf((Class<Enum>) fieldType, cursor.getString(columnIndex));
                    field.set(obj, enumValue);
                } else if (fieldType.isArray()
                        && (fieldType.getComponentType() == byte.class || fieldType.getComponentType() == Byte.class)) {
                    field.set(obj, cursor.getBlob(columnIndex));
                } else {
                }
            } catch (IllegalAccessException ignored) {
            }
        }
        return obj;
    }

    /**
     * Author: JFangZ
     * Email:janezhang1901@163.com
     * Date:2018/11/15 21:50
     * Description:初始化数据库，使用数据库之前必须先调用该方法进行初始化
     * 否则会抛出异常 IllegalStateException("DatabaseManager 没有初始化");
     * 可配置不同环境是否输出 sql 语句
     * android {
     * buildTypes {
     * debug {
     * resValue("bool", "showSql", "true")
     * }
     * release {
     * minifyEnabled false
     * proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
     * resValue("bool", "showSql", "false")
     * }
     * }
     * }
     */
    public void init(DatabaseHelper databaseHelper) {
        mDatabaseHelper = databaseHelper;
        // 获取数据库可读可写的操作对象
        mSqLiteDatabase = databaseHelper.getWritableDatabase();
    }

    /**
     * Author: JFangZ
     * Email:janezhang1901@163.com
     * Date:2020/4/19 21:50
     * Description:获取预定义 sql 语句
     *
     * @param type   被代理的类
     * @param method 被调用的方法
     */
    private String getSql(Type type, Method method) {
        String value = null;
        if (method.getAnnotation(Insert.class) != null) {
            value = method.getAnnotation(Insert.class).value();
        } else if (method.getAnnotation(Delete.class) != null) {
            value = method.getAnnotation(Delete.class).value();
        } else if (method.getAnnotation(Update.class) != null) {
            value = method.getAnnotation(Update.class).value();
        } else if (method.getAnnotation(Select.class) != null) {
            value = method.getAnnotation(Select.class).value();
        }
        Map<String, String> map = mDatabaseHelper.mPresetSqlMap.get(type);
        if (map == null) {
            return value;
        }
        // 先查询是否是已安装的内置的 sql
        if (!TextUtils.isEmpty(map.get(value))) {
            // 如果不是,则使用 value 作为需要执行的预定义 sql
            return map.get(value);
        }
        return value;
    }

    public <T> T getDao(final Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (mDatabaseHelper == null) {
//                    throw new IllegalStateException("Please init.");
                    return null;
                }
                // 获取该接口真实泛型,第一个真实泛型是表的映射类
                Type[] genericInterfaces = clazz.getGenericInterfaces();
                if (genericInterfaces.length == 0) {
                    throw new Exception();
                }
                Type type = genericInterfaces[0];
                if (!(type instanceof ParameterizedType)) {
                    throw new Exception();
                }
                Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
                if (actualTypeArguments.length == 0) {
                    throw new Exception();
                }
                // 获取注解上的 sql 语句
                String sql = getSql(actualTypeArguments[0], method);
                // 获取参数集合
                Map<String, Object> paramMap = getParamMap(method, args);
                Cursor cursor = null;
                try {
                    if (method.getAnnotation(Insert.class) != null) {
                        try {
                            TableInfoBean tableInfoBean = mDatabaseHelper.mTableInfoBeanMap.get(actualTypeArguments[0]);
                            // 将参数集合替换预定义 sql 中对应的占位符,并填充参数值
                            SQLiteStatement sqLiteStatement = formatSql(clazz.getSimpleName(), sql, paramMap);
                            long lastRowInserted = sqLiteStatement.executeInsert();
                            List<PrimaryKeyColumnInfoBean> primaryKeyColumnInfoBeanList = tableInfoBean.getPrimaryKeyColumnInfoBeanList();
                            if (isObjParam(method)
                                    && primaryKeyColumnInfoBeanList.size() == 1
                                    && primaryKeyColumnInfoBeanList.get(0).isAutoIncrement()
                                    && primaryKeyColumnInfoBeanList.get(0).isUseGeneratedKeys()) {
                                // 返回自增长 id
                                Object arg = args[0];
                                Field idField = arg.getClass().getDeclaredField(primaryKeyColumnInfoBeanList.get(0).getFieldName());
                                if (!idField.isAccessible()) {
                                    idField.setAccessible(true);
                                }
                                idField.set(arg, primaryKeyColumnInfoBeanList.get(0).convertIdValue(lastRowInserted));
                            }
                            return 1;
                        } catch (SQLException ignored) {
//                            ignored.printStackTrace();
                            return 0;
                        }
                    } else if (method.getAnnotation(Delete.class) != null) {
                        try {
                            SQLiteStatement sqLiteStatement = formatSql(clazz.getSimpleName(), sql, paramMap);
                            return sqLiteStatement.executeUpdateDelete();
                        } catch (SQLException ignored) {
                            ignored.printStackTrace();
                            return 0;
                        }
                    } else if (method.getAnnotation(Update.class) != null) {
                        try {
                            SQLiteStatement sqLiteStatement = formatSql(clazz.getSimpleName(), sql, paramMap);
                            return sqLiteStatement.executeUpdateDelete();
                        } catch (SQLException ignored) {
//                            ignored.printStackTrace();
                            return 0;
                        }
                    } else if (method.getAnnotation(Select.class) != null) {
                        try {
                            sql = formatQuerySql(clazz.getSimpleName(), sql, paramMap);
                            cursor = mSqLiteDatabase.rawQuery(sql, new String[]{});
                            Type returnType = method.getGenericReturnType();
                            if (returnType == int.class || returnType == Integer.class) {
                                // 返回 int
                                if (cursor.moveToNext() && cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                    return Integer.valueOf(cursor.getInt(0));
                                }
                                return 0;
                            } else if (returnType == long.class || returnType == Long.class) {
                                // 返回 long
                                if (cursor.moveToNext() && cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                    return Long.valueOf(cursor.getLong(0));
                                }
                                return 0l;
                            } else if (returnType == float.class || returnType == Float.class) {
                                // 返回 float
                                if (cursor.moveToNext() && cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                    return Float.valueOf(cursor.getFloat(0));
                                }
                                return 0f;
                            } else if (returnType == double.class || returnType == Double.class) {
                                // 返回 double
                                if (cursor.moveToNext() && cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                    return Double.valueOf(cursor.getFloat(0));
                                }
                                return 0d;
                            } else if (returnType == boolean.class || returnType == Boolean.class) {
                                // 返回 boolean
                                if (cursor.moveToNext() && cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                    return Boolean.valueOf(cursor.getString(0));
                                }
                                return false;
                            } else if (returnType == String.class) {
                                // 返回 String
                                if (cursor.moveToNext() && cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                    return cursor.getString(0);
                                }
                                return null;
                            } else {
                                // 其他类型暂时都统一作为对象处理
                                if (returnType instanceof ParameterizedType) {
                                    // 如果 returnType 为 ParameterizedType 则是使用了泛型,认为是集合,接口现在要不要只支持 list 呢
                                    ((ParameterizedType) returnType).getActualTypeArguments();
                                    if (((ParameterizedType) returnType).getActualTypeArguments().length == 0) {
                                        return new ArrayList<>();
                                    }
                                    String className;
                                    if (mDatabaseHelper.mPresetSqlMap.get(actualTypeArguments[0]).get(method.getAnnotation(Select.class).value()) != null) {
                                        // 内置的 sql,类型为该接口泛型的一个真实类型
                                        className = actualTypeArguments[0].toString().replace("class ", "");
                                    } else {
                                        className = ((ParameterizedType) returnType).getActualTypeArguments()[0].toString().replace("class ", "");
                                    }
                                    List<Object> list = new ArrayList();
                                    while (cursor.moveToNext()) {
                                        if (TextUtils.equals("java.lang.Integer", className)) {
                                            // 返回 int
                                            if (cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                                list.add(Integer.valueOf(cursor.getInt(0)));
                                            }
                                        } else if (TextUtils.equals("java.lang.Long", className)) {
                                            // 返回 long
                                            if (cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                                list.add(Long.valueOf(cursor.getLong(0)));
                                            }
                                        } else if (TextUtils.equals("java.lang.Float", className)) {
                                            // 返回 float
                                            if (cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                                list.add(Float.valueOf(cursor.getFloat(0)));
                                            }
                                        } else if (TextUtils.equals("java.lang.Double", className)) {
                                            // 返回 double
                                            if (cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                                list.add(Double.valueOf(cursor.getFloat(0)));
                                            }
                                        } else if (TextUtils.equals("java.lang.Boolean", className)) {
                                            // 返回 boolean
                                            if (cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                                list.add(Boolean.valueOf(cursor.getString(0)));
                                            }
                                        } else if (TextUtils.equals("java.lang.String", className)) {
                                            // 返回 String
                                            if (cursor.getType(0) != Cursor.FIELD_TYPE_NULL) {
                                                list.add(cursor.getString(0));
                                            }
                                        } else {
                                            // 其他类型暂时都统一作为对象处理
                                            Object obj = getObj(className, cursor);
                                            if (obj != null) {
                                                list.add(obj);
                                            }
                                        }
                                    }
                                    return list;
                                } else {
                                    // 单个对象
                                    String className = "";
                                    if (mDatabaseHelper.mPresetSqlMap.get(actualTypeArguments[0]).get(method.getAnnotation(Select.class).value()) != null) {
                                        // 内置的 sql,类型为该接口泛型的一个真实类型
                                        className = actualTypeArguments[0].toString().replace("class ", "");
                                    } else {
                                        className = returnType.toString().replace("class ", "");
                                    }
                                    if (cursor.moveToNext()) {
                                        return getObj(className, cursor);
                                    }
                                    return null;
                                }
                            }
                        } catch (SQLException ignored) {
//                            ignored.printStackTrace();
                            return null;
                        }
                    }
                } finally {
                    if (cursor != null) {
                        cursor.close();
                    }
                }
                return null;
            }
        });
    }

    public void beginTransaction() {
        mSqLiteDatabase.beginTransaction();
    }

    public void beginTransactionNonExclusive() {
        mSqLiteDatabase.beginTransactionNonExclusive();
    }

    public void setTransactionSuccessful() {
        mSqLiteDatabase.setTransactionSuccessful();
    }

    public void endTransaction() {
        mSqLiteDatabase.endTransaction();
    }

    public void close() {
        mSqLiteDatabase.close();
    }

    public boolean isOpen() {
        return mSqLiteDatabase != null && mSqLiteDatabase.isOpen();
    }

    // TODO tmp
    public SQLiteDatabase getSqLiteDatabase() {
        return mSqLiteDatabase;
    }
}
